import libtcodpy as libtcod
import math, time
import astar2

import ent, ent_furniture
import events, player
import sk_math, sk_map

#ai_states = ("IDLE", "CHASING", "ROAMING", "RETREATING")
ai_state_IDLE = 0
ai_state_CHASING = 1
ai_state_ESCAPING = 2
ai_state_ROAMING 	= 	3

ai_state_TIRED 		=	10	#look for bed/cardbox
ai_state_SLEEPING 		=	11	#dreams

ai_state_PATROLE = 50	#police

ai_state_INVESTIGATING = 51 #police



AI_MAX_TURN_LEN = 10		#in seconds
AI_TURN_LEN = 0

import sys, time

class ActorController(events.EventListener):
	def __init__(self):
		self.state = "IDLE"
		
		self.path = None		#movement path to the rarget
		self.target = None
		
		self.last_node = None	#store last tile for shit like closing doors, etc
		
	
	def get_action(self):
		pass
		
	def player_in_fov(self):
		owner = self.owner
		player = owner.em.services["player"]	
		fov = owner.em.services["fov"]	
		if fov.tile_visible(owner.x, owner.y):
			return True
			
		return False
		
	def think(self):
		#if not self.owner.is_alive():
		#	return
			
		if self.player_in_fov():
			self.e_see_player()
			
		self.update()
	
	def e_see_player(self):
		pass
		
	def update(self):
		if self.state == ai_state_ESCAPING:
			self.model = "!"
		else:
			self.model = "@"
		
	def calculate_path(self, target):
		#---------------------------------------------------------------
		#	It takes a lot of time to recalculate all pathes at the start of the game
		#	We'll just not allow to call too much self::calculate_path()'es
		#---------------------------------------------------------------
		#global AI_TURN_LEN, AI_MAX_TURN_LEN
		
		#if AI_TURN_LEN >= AI_MAX_TURN_LEN:
		#	return None
		#---------------------------------------------------------------
		#ts_start = time.time()
		#---------------------------------------------------------------
		self.target = target
		
		self.owner.em.services["pathfinder"].add_request(self)
		
		#---------------------------------------------------------------
		#ts_end = time.time()
		#diff = ts_end-ts_start
		#AI_TURN_LEN = AI_TURN_LEN + diff
		#---------------------------------------------------------------
		
		return self.path
		
	def get_model(self):
		if self.state == ai_state_CHASING:
			return "!"
		elif self.state == ai_state_SLEEPING:
			return "Z"
		
#=======================================================================
class BaseAction(object):
	def __init__(self,ai):
		self.actor = ai.owner
		self.ai = ai
		
	def get_service(self,name):
		return self.actor.em.services[name]
	#-------------------------------------------------------------------	
	#helper function	
	def move2player_vec(self, target_x, target_y):
		#vector from this object to the target, and distance
		dx = target_x - self.actor.x
		dy = target_y - self.actor.y
		distance = math.sqrt(dx ** 2 + dy ** 2)
 
		#normalize it to length 1 (preserving direction), then round it and
		#convert to integer so the movement is restricted to the map grid
		dx = int(round(dx / distance))
		dy = int(round(dy / distance))
		#self.owner.move(dx, dy)
		return (dx,dy)
	
	#TODO: move to math
	#-------------------------------------------------------------------	
	def distance_to(self, other):
		#return the distance to another object
		dx = other.x - self.actor.x
		dy = other.y - self.actor.y
		return math.sqrt(dx ** 2 + dy ** 2)
		
	#-------------------------------------------------------------------	
	def get_nearest(self,ent_list):
		nearest = None
	
		for obj in ent_list:
			if isinstance(obj,ent.Entity):
				if not nearest:
					nearest = obj
				else:
					if self.distance_to(obj) < self.distance_to(nearest):
						nearest = obj
				
			else:
				print "DEBUG WARNING! ai.py BaseAction::get_nearest() -  invalid entity in entity list"
				
		return nearest
	#-------------------------------------------------------------------
	
	def act(self):
		pass
		
				
	def follow_path(self):
		ai = self.ai
		map = self.ai.owner.em.services["map"]
		
		if ai.path and len(ai.path):
			node = ai.path[0]
			x,y = node
			
			if not map.is_blocked(x,y):
				#del ai.path[0]
				
				ai.last_node = (self.actor.x, self.actor.y)
				ai.path.remove(node)
				self.e_on_move(x,y)
			else:
				pass
				#TODO: recalculate route
				self.e_on_obstacle(x,y)
				
	def e_on_obstacle(self,x,y):
		pass
		
	def e_on_move(self,x,y):
		#print "moving @ %d,%d" % (x,y)
		self.actor.move(x-self.actor.x,y-self.actor.y)
#=======================================================================
class ActionRoam(BaseAction):
	def act(self):
		#limit random movements a lot (more natural way)
		if libtcod.random_get_int(0, 0, 100) < 30:
			dx = libtcod.random_get_int(0, -1, 1)
			dy = libtcod.random_get_int(0, -1, 1)
			self.actor.move(dx,dy)
			
#=======================================================================
class ActionChase(BaseAction):
	def act(self):
		player = self.actor.em.services["player"]
		ai = self.ai
		
		#if not ai.path:
		ai.calculate_path(player)
			
		self.follow_path()
		
	def e_on_obstacle(self,x,y):
		map = self.get_service("map")
		player_ent = self.get_service("player")
		
		obstacle = map.get_obstacle(x,y)
		
		if isinstance(obstacle,player.PlayerEnt):
			self.actor.attack(x,y)
		else:
			self.ai.calculate_path(player_ent)
			
#=======================================================================			
class ActionPatrole(BaseAction):
	def act(self):
		ai = self.ai
		
		if not ai.path:
			self.get_random_route()
			
		#add 10% chance of standing still, making it more natural for player to chase target	
		if libtcod.random_get_int(0, 0, 100) >= 15:
			self.follow_path()
		
	def get_random_route(self):
		
		ai = self.ai
		mst = self.actor.em.services["milestones"]
		rnd_mst = mst[libtcod.random_get_int(0, 0, len(mst)-1)]
			
		#get random point near the milestone node (more natural ai behavior)
		point = sk_math.Point(
			rnd_mst.x + libtcod.random_get_int(0, -2, 2),
			rnd_mst.y + libtcod.random_get_int(0, -2, 2)
		)
			
		ai.calculate_path(point)	
	
	def e_on_obstacle(self,x,y):
		#recalculate route on obstacle
		
		#TODO: save route target to recalculate route without changing it?
		self.get_random_route()

#-----------------------------------------------------------------------		
class ActionInvestigate(BaseAction):
	def act(self):
		self.calc_path()
		self.follow_path()
		
		crimeplaces 	= self.get_service("crimeplaces")
		objects 		= self.get_service("objects")
		player 			= self.get_service("player")
		_cp = self.get_nearest(crimeplaces)
		
		#if we are near the crimeplace
		#---------------------------------------------------------------
		if self.actor.combat:
			fov_radius = self.actor.combat.get_fov()
		else:
			fov_radius = 10
			
		if not _cp:
			self.ai.state = ai_state_PATROLE
			return
			
		if self.distance_to(_cp) <= fov_radius:
			#IF WE DETECTED PLAYER, CHASE HIM
			#print "WE ARE IN THE CRIMEPLACE"
			
			if self.ai.player_in_fov():
				#print "PLAYER IN FOV!!!"
				
				self.actor.model = "!"
				self.ai.state = ai_state_CHASING
				return
			#OTHERWISE COLLECT CLUES, IF ANY IS NEAR
			#---------------------------------------------------------------
			
			#THEN CLOSE THE CASE
			crimeplaces.remove(_cp)
			objects.remove(_cp)
			
			del _cp
			
			print "Case is closed"
	
	def calc_path(self):
		crimeplaces = self.get_service("crimeplaces")
		ai = self.ai
		
		nearest_crimeplace = self.get_nearest(crimeplaces)
		
		if not ai.path:
			if nearest_crimeplace:
				ai.calculate_path(nearest_crimeplace)
			else:
				print "DEBUG - no nearest crimeplace!!!!!"
		
	def e_on_obstacle(self,x,y):
		map = self.get_service("map")
		
		obstacle = map.get_obstacle(x,y)
		
		if isinstance(obstacle,ent_furniture.Furniture):
			self.actor.attack(x,y)
		else:
			self.calc_path()		#recalculate path

#=======================================================================
class ActionSleep(BaseAction):
	def act(self):
		#WE are sleeping. Do nothing.
		#TODO: check for sounds and alerts based on character perception
		pass
		
#	search for own bed if any		
class ActionTired(BaseAction):
	def act(self):
		#self.ai.state = ai_state_SLEEPING
		
		if not self.ai.path:
			if self.in_bed():
				self.ai.state = ai_state_SLEEPING
			else:
				self.calc_path()
				
		#===============================================================
		#special hack to force NPC close door after he opened it
		#===============================================================
		map = self.get_service("map")
		if self.ai.last_node:
			for obj in map.get_tile(self.ai.last_node[0],self.ai.last_node[1]).objects:
				if isinstance(obj, ent_furniture.Door) and not obj.locked:
					#TODO: check if npc actualy OWN this tile
					obj.lock()
					return	#skip turn
		#===============================================================
				
			
		self.follow_path()
		
	#check if npc is in bed		
	def in_bed(self):
		if not self.actor.apt:
			return False
			
		#safe switch
		#things are pretty fucking up there	
		
		if not self.actor.em.services["sk_map_apt_beds"].has_key(self.actor.apt):
			return False
			
		bed_list = self.actor.em.services["sk_map_apt_beds"][self.actor.apt]
		for bed in bed_list:
			if (bed.x,bed.y) == (self.actor.x,self.actor.y):
				return True
	
	def calc_path(self):
		
		if not self.actor.apt:
			return	
		
		if not self.actor.em.services["sk_map_apt_beds"].has_key(self.actor.apt):
			return
		
		bed_list = self.actor.em.services["sk_map_apt_beds"][self.actor.apt]
		bed = bed_list[libtcod.random_get_int(0,0,len(bed_list)-1)]
		
		self.ai.calculate_path(bed)

	def e_on_obstacle(self,x,y):
		map = self.get_service("map")
		
		obstacle = map.get_obstacle(x,y)
		
		if isinstance(obstacle,ent_furniture.Door):
			obstacle.unlock()
		elif isinstance(obstacle,ent_furniture.Furniture):
			self.actor.attack(x,y)
		else:
			self.calc_path()		#recalculate path

#=======================================================================

class ActionEscape(BaseAction):
	def act(self):
		player = self.actor.em.services["player"]
		vec = self.move2player_vec(player.x,player.y)
		
		if self.distance_to(player) > 0 :
			if self.distance_to(player) < 10:
				self.actor.move(-vec[0],-vec[1])
				
		elif player.is_alive():
			#self.actor.attack(player.x, player.y)
			#TODO: scream
			self.actor.em.services["render"].message(self.actor.name + " screams!", libtcod.red)
			pass

#=======================================================================

class BaseAI(ActorController):
	def __init__(self):
		super(BaseAI,self).__init__()
		
		self.owner = None
		#self.state = ai_state_CHASING
		#pass
		
	def get_action(self):
		if self.state == ai_state_CHASING:
			return ActionChase(self)
		elif self.state == ai_state_ESCAPING:
			return ActionEscape(self)
		else:	
			return ActionRoam(self)
		
	def think(self):
		
		if not self.owner.is_alive():
			return
		
		owner = self.owner
		player	= owner.em.services["player"]	
		map		= owner.em.services["map"]
		
		if self.player_in_fov():
			if map.get_tile(player.x,player.y).owned_by(owner):
				owner.model = "!"
				self.state = ai_state_CHASING
				
			if self.owner.hp < self.owner.combat.get_max_hp()/2:
				self.state = ai_state_ESCAPING
		
		action = self.get_action()
		action.act()
		
	def e_on_take_damage(self,inflictor):
		self.state = ai_state_CHASING
		
#=======================================================================	
#PEDESTRIAN AI - patroling from node to node like a policeman
#=======================================================================
class PedestrianAI(ActorController):
	
	def get_action(self):
		if self.state == ai_state_CHASING:
			return ActionChase(self)
		elif self.state == ai_state_ESCAPING:
			return ActionEscape(self)
		elif self.state == ai_state_TIRED:
			return ActionTired(self)
		elif self.state == ai_state_SLEEPING:
			return ActionSleep(self)
		else:	
			return ActionPatrole(self)
			
	def think(self):
		if not self.owner.is_alive():
			return	
			
		timer	= self.owner.em.services["sk_time"]
		if timer.is_night():
			if not self.state == ai_state_SLEEPING:
				self.state = ai_state_TIRED
		else:
			self.state = None
				
			
		if self.player_in_fov():
			
			if self.state == ai_state_SLEEPING:
				#if hear loud sound, than awake
				
				#otherwise:
				return
				
				
			#TODO: check if crimeplace is near and catch player
			player = self.owner.em.services["player"]
			if player.bloody and player.is_alive():
				#RUN FOR YOUR LIFE
				self.state = ai_state_ESCAPING
		
		action = self.get_action()
		action.act()
		
	def notify(self,event):
		if isinstance(event,events.SuspiciousSoundEvent):
			#TODO: beta version: NPC sends message directly to the cop.
			#future versions: NPC should manualy locate cop and notify him
			
			self.owner.em.post(
				events.NPCReportCrimeEvent(
					event.origin.x,
					event.origin.y
				)
			)
			
		if isinstance(event,events.NPCWitnessCrimeEvent):
			self.state = ai_state_ESCAPING
			self.owner.model = "!"
			
		if isinstance(event,events.MurderEvent):
			if event.victim == self.owner.em.services["player"]:
				self.state = None
		
		
#=======================================================================	
#POLICE AI - patroling from node to node
#=======================================================================
class PoliceAI(ActorController):
	def __init__(self):
			super(PoliceAI,self).__init__()
			self.owner = None
			self.state = ai_state_PATROLE
			
			self.target = None

	def get_action(self):
		if self.state == ai_state_PATROLE:
			return ActionPatrole(self)
		elif self.state == ai_state_CHASING:
			return ActionChase(self)
		elif self.state == ai_state_INVESTIGATING:
			return ActionInvestigate(self)
		else:	
			return ActionRoam(self)
		
	def think(self):
		super(PoliceAI,self).think()
		
		action = self.get_action()
		action.act()
		
	def e_see_player(self):
		player = self.owner.em.services["player"]
		if player.bloody:
				#let's catch this sneeky bastard
			self.state = ai_state_CHASING
			
		if not player.is_alive():
			self.state = ai_state_PATROLE
		
	def notify(self,event):	
	
		#print "Police AI: checking if event is Murder"
		
		if isinstance(event,events.NPCReportCrimeEvent):
			print "PoliceAI:: starting investigation"
			
			self.state = ai_state_INVESTIGATING
			self.path = []
			
	def e_on_take_damage(self,inflictor):
		self.state = ai_state_CHASING

		
